[Разбор] Полный разбор проекта MyBlog

Введение

MyBlog – это легковесное асинхронное веб-приложение для ведения личного блога и галереи нейросетевых артов. Построено на базе фреймворка FastAPI. Основная идея проекта – полная автономность, отсутствие тяжелых внешних зависимостей (баз данных) и прямая работа с файловой системой. Это делает проект идеальным для self-hosted развертывания.

1. Архитектура и серверная логика: main.py

1.1 Инфраструктурные решения

Проект спроектирован с учетом работы за Reverse Proxy (Nginx/Traefik).

@app.middleware("http")
  async def add_proxy_headers(request: Request, call_next):
      path_prefix = request.headers.get("X-Forwarded-Prefix")
      if path_prefix:
          request.scope["root_path"] = path_prefix
      return await call_next(request)

Это критически важный узел. Если сайт крутится не в корне домена (например, domain.com/blog/), заголовки X-Forwarded-Prefix позволяют FastAPI корректно генерировать внутренние ссылки.

1.2 Работа с базой данных (Flat-file DB)

Архитектура проекта исключает SQL. Мы используем Flat-file approach:

Плюсы: Бекап блога – это просто копирование папки. Нет оверхеда на коннекты к БД.
Минусы: Отсутствие транзакционности. При массовой записи возможен Race Condition, но для персонального блога это не является проблемой.

1.3 Система авторизации

Реализована через SessionMiddleware на базе зашифрованных Cookies.

1.4 Контент-пайплайн (Markdown)

html = markdown.markdown(text, extensions=['fenced_code', 'tables', 'codehilite'])

Сервер использует динамический рендеринг: при каждом запросе выбранный .md файл считывается с диска и преобразуется в HTML. Это избавляет от необходимости хранить базу данных с HTML-копиями статей и позволяет обновлять контент простым редактированием файла на сервере.

Техническое уточнение:
В текущем вызове markdown.markdown(text, extensions=['fenced_code', 'tables', 'codehilite']) расширение codehilite является избыточным (рудиментом), так как выполняет разметку синтаксиса на стороне сервера без последующей стилизации на фронтенде. Для оптимизации и очистки выходного HTML данный модуль может быть удалён без потери визуального качества.

2. Функциональные блоки (Эндпоинты)

2.1 Галерея и Промты

Галерея – это не просто список картинок. Сервер связывает файлы изображений с их текстовыми описаниями (промтами):

2.2 Загрузка контента

Для статей и галереи реализованы асинхронные загрузчики:

2.3 Трюки с вёрсткой (Jinja2 Filters)

Внедрен кастомный фильтр insert_zwsp:

templates.env.filters['insert_zwsp'] = insert_zero_width_spaces

Он автоматически вставляет невидимые пробелы в длинные строки (более 10 символов), которые не являются HTML-тегами. Это спасает мобильную вёрстку от "разрывов" из-за длинных слов или ссылок.

3. Обоснование стека

  1. FastAPI: Минималистичен, быстр, асинхронен. Позволяет легко расширять проект.
  2. Uvicorn: ASGI-сервер, обеспечивающий высокую пропускную способность.
  3. Markdown: Выбран как стандарт де-факто для написания заметок (идеальная совместимость с Obsidian).
  4. Jinja2: Серверный рендеринг исключает необходимость в сложных фронтенд-фреймворках, сохраняя при этом интерактивность.

4. Итоги и эксплуатация

Архитектурный вывод: Это надежное монолитное решение "всё в одном", которое легко разворачивается через Docker или просто как системный процесс.

2. Клиентская часть: main.html (Главная страница)

2.1 UI/UX и дизайн-система

Дизайн выполнен в стиле Modern Dark Minimalism. Основной упор сделан на контенте, при этом интерфейс визуально перекликается с профессиональными IDE и инструментами продуктивности.

2.2 Умная система категорий (The Tag System)

Одной из самых интересных фишек является автоматический парсинг категорий из названий файлов.

const match = name.match(/^\[(.*?)\]/);
  if (match) {
    categories.add(match[1]);
    item.dataset.category = match[1].toLowerCase();
  }

Это избавляет админа от необходимости вручную прописывать теги в базе данных. Система сама строит дерево категорий на лету.

2.3 Динамическая фильтрация и поиск

Вместо того чтобы нагружать сервер запросами при каждом вводе буквы, поиск реализован на стороне клиента (Client-side Filtering).

  1. Поиск: Работает мгновенно через includes() по атрибутам data-name.
  2. Категории: Переключаются без перезагрузки страницы. Пользователь видит результат сразу.
  3. Счётчик: Элемент #articles-count динамически обновляется, показывая количество найденных записей. Это отличный UX-паттерн.

2.4 AJAX-управление (Админ-панель)

Для авторизованных пользователей предусмотрена возможность управления архивом.

2.5 Технические особенности вёрстки


3. Обоснование архитектурных решений фронтенда

  1. No Framework (Vanilla JS): Использование чистого JavaScript \u2014 осознанный выбор. Для такого функционала React или Vue были бы избыточны (overkill), а текущий код весит всего пару килобайт и работает быстрее любого фреймворка.
  2. Jinja2 Hydration: Начальный список статей рендерится сервером, а затем "подхватывается" скриптами для фильтрации. Это сохраняет SEO-показатели (поисковики видят все ссылки) при полной интерактивности.
  3. LocalStorage Bypass: В отличие от предыдущих модулей, здесь данные полностью контролируются сервером, а фронтенд лишь управляет их отображением.

Плюсы:

Минусы:

Полный разбор архитектуры MyBlog: Article View Engine

Этот модуль (article.html) отвечает за рендеринг и обогащение контента. Основная фишка заключается в Post-processing на стороне клиента. Мы не просто выводим HTML, мы его дорабатываем скриптами для лучшего UX

1. Клиентская обработка контента (Post-processing)

1.1 Оживление ссылок

1.1 Оживление ссылок

Стандартный парсер Markdown может игнорировать текстовые URL и не превращать их в кликабельные элементы. Данный скрипт автоматизирует процесс: он находит в тексте строки, соответствующие паттерну https://, и оборачивает их в активные HTML-ссылки. Это гарантирует корректную навигацию, даже если автор забыл использовать синтаксис разметки.

const urlRegex = /(?<!["=])(https?:\/\/[^\s<]+)/g;
articleBody.querySelectorAll('p, li, td').forEach(el => {
  el.innerHTML = el.innerHTML.replace(urlRegex, (url) => {
    return `<a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a>`;
  });
});

Инженерная деталь: Используется Lookbehind (?<!["=]), чтобы не испортить теги, где URL уже находится внутри атрибутов src или href.

1.2 Парсинг категорий из заголовка

Здесь применяется логика визуальных меток. Если заголовок статьи имеет вид [OS] Настройка Windows, скрипт выделяет тег [OS], преобразует его в отдельный графический элемент над названием, а основной текст очищает от лишних скобок. Такой подход позволяет сохранять профессиональную структуру статьи, оставляя исходные файлы на диске удобными для чтения.

2. Работа с кодом и таблицами

2.1 Code Blocks & Copy Engine

Любой блок кода автоматически оборачивается в контейнер .code-block, куда инжектится кнопка копирования.

Таблицы в вебе часто создают проблемы. Здесь реализовано изящное решение: скрипт находит все таблицы и оборачивает их в div.table-wrapper с overflow-x: auto. На смартфонах таблица не ломает вёрстку, а получает аккуратную горизонтальную прокрутку.

Таблицы в вебе – это один из самых «капризных» элементов. Если их просто оставить как есть, они напрочь ломают мобильную вёрстку, растягивая страницу по горизонтали так, что пользователю приходится скроллить влево-вправо.

1. Серверный этап (Генерация)

Расширение tables в библиотеке markdown превращает стандартную Markdown-разметку:

| Заголовок 1 | Заголовок 2 |
|-------------|-------------|
| Текст 1     | Текст 2     |

в чистый HTML-код: теги <table><thead><tbody><tr> (строки) и <td> (ячейки). Сами по себе эти теги не умеют подстраиваться под размер экрана.

2. Фронтенд-этап (Адаптация)

Поскольку таблицы «дубовые» по своей природе, в твоём коде (в модуле article.html) используется специальный JS-скрипт.

Что он делает (алгоритм):

  1. Находит все таблицы (<table>), которые сгенерировал сервер.
  2. Проверяет их ширину. Если таблица не влезает в экран, скрипт не даёт ей «разорвать» дизайн.
  3. Он оборачивает каждую таблицу в специальный контейнер <div> со свойством overflow-x: auto.

Как это выглядит для пользователя:
Вместо того чтобы вся страница начала «болтаться» из стороны в сторону, сама статья остаётся стабильной, а внутри неё появляется аккуратная область с таблицей, которую можно скроллить пальцем влево-вправо. Это называется Responsive Data Tables.### Итог для твоего разбора:
Таблицы в твоём проекте – это гибридное решение.

Это классический пример того, как минималистичный бэкенд дополняется умным фронтендом для обеспечения безупречного UX (User Experience).

3. Дизайн и типографика статьи

4. Интеграция с нативной системой (Share API)

Кнопка Поделиться не просто копирует URL, она выполняет нормализацию:

const prettyUrl = decodeURIComponent(window.location.href).replace(/ /g, '%20');

Это гарантирует, что если в названии статьи есть кириллица или пробелы, ссылка в буфере будет валидной и рабочей для любого мессенджера.

5. Безопасность и управление


Вердикт по модулю Article

Этот шаблон превращает простой текстовый файл в полноценную веб-страницу.
Главный плюс: Весь функционал (копирование кода, бейджи, ссылки) работает автономно и мгновенно.
Минус: Если в статье будет 500 блоков кода, инициализация 500 кнопок копирования может занять доли микросекунды (что на самом деле незаметно, но мы же задроты, верно?).

Полный разбор архитектуры MyBlog: Gallery & Masonry Engine

Галерея предназначена для отображения нейросетевых артов и видео. Основная техническая сложность здесь – работа с контентом разной высоты без создания визуальных дыр в сетке.

1. Masonry Grid: Математика вёрстки

Вместо использования тяжелых библиотек (типа Isotope или Masonry.js), в проект интегрирован самописный легковесный алгоритм на Vanilla JS.

  1. Логика работы (resizeInstance):
  2. Система определяет количество колонок в зависимости от ширины экрана (от 1 до 3).
  3. Создаётся массив colHeights, инициализированный нулями.
  4. Для каждого элемента вычисляется его позиция: он всегда встаёт в ту колонку, которая в данный момент является самой короткой.
  5. Используется translate3d для перемещения карточек – это задействует аппаратное ускорение GPU, делая анимацию плавной (60 FPS), в отличие от изменения top/left.
const minH = Math.min(...colHeights), idx = colHeights.indexOf(minH);
item.style.transform = `translate3d(${idx * (itemWidth + gap)}px, ${minH}px, 0)`;

2. Работа с медиа-контентом

2.1 Поддержка видео

Галерея нативно поддерживает формат MP4. Для экономии ресурсов и создания "живого" эффекта превью:

2.2 Умный Лайтбокс (Просмотрщик)

Реализована собственная система предпросмотра. При клике на изображение создаётся оверлей с динамическим блюром (backdrop-filter: blur(15px)).
Инженерная деталь: Контент внутри лайтбокса адаптивен (max-height: 92vh). Это предотвращает появление полос прокрутки внутри модального окна на маленьких экранах.


3. Интеграция с данными ИИ (Prompt Management)

Каждая карточка в галерее – это контейнер для метаданных.


4. Дизайн и UX-фишки


5. Плюсы и минусы решения

Плюсы:

  1. Zero Library: Отсутствие внешних JS-зависимостей делает страницу экстремально легкой.
  2. GPU acceleration: Плавная перестройка сетки при изменении размера окна браузера.
  3. Flexibility: Галерея одинаково хорошо работает как с горизонтальными, так и с вертикальными артами.

Минусы:

  1. Initial Load: Функция resizeInstance должна ждать полной загрузки изображений, чтобы узнать их высоту (window.onload). На очень медленном интернете сетка может "прыгнуть" во время инициализации. Решается введением фиксированных пропорций для картинок (aspect-ratio), если они известны заранее.

Полный разбор MyBlog: Система доступа и управления контентом

1. Модуль авторизации: login.html

Страница входа выполнена в стиле Cyber-Dark. Здесь нет ничего лишнего, только функционал, защищённый логикой сессий.

1.1 Механизм редиректов (The "Next" Pattern)

Одной из ключевых особенностей является использование переменной next_url.

1.2 Фронтенд-валидация


2. Модули загрузки: upload_article.html / upload_gallery.html

Это интерфейсы для взаимодействия с файловой системой сервера через абстракцию FastAPI UploadFile.

2.1 Drag-and-Drop и Пакетная обработка

Система поддерживает одновременную загрузку нескольких файлов (multiple).

2.2 Логика очистки имен (Sanitization)

На странице загрузки статей реализована важная проверка:

safe_name = re.sub(r'[^\w\s\-\.\[\]]', '', file.filename).strip()

Это защищает сервер от инъекций в именах файлов. Если файл называется [AI]; rm -rf /; .md, он превратится в безобидный [AI] rm -rf .md.


3. Административные операции (Destructive Actions)

Проект включает в себя функции \u00abядерного уничтожения\u00bb данных для быстрой очистки архива.

  1. delete_all: Эти эндпоинты защищены двойным барьером.
  2. Кнопка на фронте вызывает confirm().
  3. Декоратор require_auth на бэкенде проверяет наличие активной сессии.
  4. Асинхронное удаление: При удалении одной статьи используется fetch, чтобы не обновлять страницу – это современный стандарт (SPA-подобное поведение).  Полный разбор архитектуры MyBlog: Data Ingestion (Загрузка данных)

Этот раздел посвящён страницам upload_gallery.html и upload_article.html. С точки зрения архитектуры – это порты ввода данных, реализующие паттерн Client-side File Processing перед отправкой на FastAPI.

1. Стек и UX-инженерия

Обе страницы используют единый визуальный код, но разные механизмы валидации в зависимости от типа контента.

1.1 Универсальный Drag-and-Drop

Вместо стандартных и скучных кнопок выбора файлов реализована интерактивная зона – dropzone.

1.2 Асинхронные пайплайны загрузки

Для отправки данных используется асинхронный fetch с объектом FormData.
Инженерный профит: Страница не перезагружается в процессе отправки. Пользователь видит статус ("Загрузка на сервер...", "Успешно!") в реальном времени.


2. Глубокий разбор: Загрузка в Галерею (upload_gallery)

Этот модуль сложнее, так как он работает с бинарными данными (изображения и видео).

2.1 Двойная валидация

Перед отправкой фронтенд выполняет первичную проверку:

  1. По MIME-типу['image/jpeg', 'image/png', 'image/gif', 'video/mp4'].includes(file.type).
  2. По расширению: На случай, если браузер некорректно определил тип файла.

Это экономит трафик: если пользователь случайно выберет .zip архив, он получит ошибку мгновенно, без ожидания ответа от сервера.### 2.2 Обработка результатов
После загрузки сервер возвращает JSON со списком результатов. Скрипт анализирует его:

const successCount = data.results.filter(r => r.status === 'ok').length;

Это позволяет реализовать частичный успех: если из 10 файлов 8 загрузились, а 2 были повреждены, пользователь узнает точное количество.

3. Глубокий разбор: Загрузка Статей (upload_article)

Этот модуль адаптирован под текстовый Workflow (Obsidian/Notepad++).

3.1 Фильтрация формата .md

В отличие от галереи, здесь кнопка "Опубликовать" (#uploadBtn) изначально заблокирована (disabled). Она "оживает" только тогда, когда в массив selectedFiles попадают файлы с расширением .md.

selectedFiles = Array.from(files).filter(f => f.name.toLowerCase().endsWith('.md'));

3.2 Автоматизация редиректа

После успешной публикации статьи пользователь автоматически перенаправляется на главную страницу блога через 1.5 секунды. Этого времени достаточно, чтобы увидеть сообщение об успехе, после чего система сразу показывает обновленный список постов.

4. Обоснование инфраструктурных решений

  1. No Heavy Dependencies: Весь функционал drag-and-drop написан на чистом JS без библиотек типа Dropzone.js. Это гарантирует отсутствие конфликтов и минимальный размер страницы (~3-5 Кб).
  2. Security by Design:
  3. На фронтенде ограничены типы файлов (accept=".md", accept="image/*").
  4. На бэкенде (как мы помним из разбора main.py) стоит повторная проверка. Это классическая стратегия Defense in Depth (эшелонированная оборона).
  5. Mobile-First: На мобильных устройствах зона dropzone адаптируется по высоте, а кнопки выбора файлов становятся крупнее, чтобы по ним было легко попасть пальцем.

5. Плюсы и минусы загрузчиков

Плюсы:

Минусы:

Модули загрузки превращают статичное хранилище в динамическую платформу. Использование fetch и FormData делает процесс публикации контента почти мгновенным, превращая MyBlog в полноценную CMS (Content Management System) для личного использования.

Полный разбор архитектуры MyBlog: Authentication Gate

Здесь реализован классический паттерн Stateful Auth, где сервер контролирует доступ на основе сессионных кук, а фронтенд обеспечивает удобную точку входа.

1. UX-инженерия и "Mobile First"

Как и во всём проекте, здесь соблюдён строгий стандарт адаптивности.

2. Логика перенаправлений (Next-параметр)Особое внимание уделено сохранению пути пользователя:

<input type="hidden" name="next_url" value="{{ next_url }}">

Техническая ценность: Если вы, будучи неавторизованным, попытаетесь зайти в панель загрузки статьи, система перекинет вас на логин, но запомнит ваш изначальный запрос. После успешного входа FastAPI прочитает это скрытое поле и вернёт вас именно туда, куда ты шёл. Это избавляет от лишних кликов и навигации по меню заново.


Общий итог по проекту (The Big Picture)

Мы разобрали MyBlog сверху донизу:

  1. Ядро (FastAPI): Асинхронная мощь и работа с плоскими файлами (Markdown/JSON) вместо тяжелых БД.
  2. Фронтенд (Vanilla JS + Jinja2): Молниеносная скорость работы, отсутствие лишних зависимостей и библиотек.
  3. Галерея (Masonry Engine): Умная сетка на GPU-ускоренных трансформациях.
  4. Статьи (Markdown Engine): Автоматическая конвертация заметок из Obsidian в живые статьи с копированием кода и адаптивными таблицами.
  5. Админка (Data Ingestion): Удобная загрузка файлов через Drag-and-Drop и безопасная авторизация.